package online.foxnull2.foxmall.order.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import online.foxnull2.common.to.mq.OrderTo;
import online.foxnull2.common.utils.R;
import online.foxnull2.common.vo.MemberRespVo;
import online.foxnull2.foxmall.order.constant.OrderConstant;
import online.foxnull2.foxmall.order.entity.OrderEntity;
import online.foxnull2.foxmall.order.entity.OrderItemEntity;
import online.foxnull2.foxmall.order.enume.OrderStatusEnum;
import online.foxnull2.foxmall.order.exception.StockInsufficientException;
import online.foxnull2.foxmall.order.feign.CartFeignService;
import online.foxnull2.foxmall.order.feign.MemberFeignService;
import online.foxnull2.foxmall.order.feign.ProductFeignService;
import online.foxnull2.foxmall.order.feign.WareFeignService;
import online.foxnull2.foxmall.order.interceptor.LoginUserInterceptor;
import online.foxnull2.foxmall.order.mapper.OrderMapper;
import online.foxnull2.foxmall.order.service.OrderItemService;
import online.foxnull2.foxmall.order.service.OrderService;
import online.foxnull2.common.to.mq.SeckillOrderTo;
import online.foxnull2.foxmall.order.to.orderCreate.Order;
import online.foxnull2.foxmall.order.to.orderCreate.OrderCreateTo;
import online.foxnull2.foxmall.order.to.orderCreate.OrderItem;
import online.foxnull2.foxmall.order.utils.PageUtils;
import online.foxnull2.foxmall.order.utils.Query;
import online.foxnull2.foxmall.order.vo.BrandVo;
import online.foxnull2.foxmall.order.vo.SpuInfoVo;
import online.foxnull2.foxmall.order.vo.freight.FreightVo;
import online.foxnull2.foxmall.order.vo.order.OrderVo;
import online.foxnull2.foxmall.order.vo.orderConfirm.CartItem;
import online.foxnull2.foxmall.order.vo.orderConfirm.MemberReceiveAddress;
import online.foxnull2.foxmall.order.vo.orderConfirm.OrderConfirmVo;
import online.foxnull2.foxmall.order.vo.orderSubmitResp.OrderSubmitRespVo;
import online.foxnull2.foxmall.order.vo.orderSubmitResp.OrderSubmitVo;
import online.foxnull2.foxmall.order.vo.wareSkuLock.WareSkuLockVo;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderEntity> implements OrderService {

    private final ThreadLocal<OrderSubmitVo> orderSubmitVoThreadLocal = new InheritableThreadLocal<>();

    private final MemberFeignService memberFeignService;
    private final CartFeignService cartFeignService;
    private final ThreadPoolExecutor executor;
    private final WareFeignService wareFeignService;
    private final StringRedisTemplate stringRedisTemplate;
    private final ProductFeignService productFeignService;
    private final OrderItemService orderItemService;
    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public OrderServiceImpl(MemberFeignService memberFeignService,
                            CartFeignService cartFeignService,
                            ThreadPoolExecutor executor,
                            WareFeignService wareFeignService,
                            StringRedisTemplate stringRedisTemplate,
                            ProductFeignService productFeignService,
                            OrderItemService orderItemService,
                            RabbitTemplate rabbitTemplate) {
        this.memberFeignService = memberFeignService;
        this.cartFeignService = cartFeignService;
        this.executor = executor;
        this.wareFeignService = wareFeignService;
        this.stringRedisTemplate = stringRedisTemplate;
        this.productFeignService = productFeignService;
        this.orderItemService = orderItemService;
        this.rabbitTemplate = rabbitTemplate;
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo confirmOrder() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        MemberRespVo memberRespVo = LoginUserInterceptor.threadLocal.get();
        Long memberId = memberRespVo.getId();

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

        CompletableFuture<Void> memberReceiveAddressesFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberReceiveAddress> memberReceiveAddresses = memberFeignService.listReceiveAddressByMemberId(memberId);
            orderConfirmVo.setMemberReceiveAddresses(memberReceiveAddresses);
        }, executor);
        CompletableFuture<Void> cartItemsFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<CartItem> cartItems = cartFeignService.listCartItem();
            orderConfirmVo.setCartItems(cartItems);
        }, executor).thenRunAsync(() -> {
            List<CartItem> cartItems = orderConfirmVo.getCartItems();
            List<Long> skuIds = cartItems.stream().map(CartItem::getSkuId).collect(Collectors.toList());
            Set<Long> skuIdSet = null;
            try {
                skuIdSet = wareFeignService.getSkuHasStock(skuIds);
            } catch (Exception e) {
                log.error("wareFeignService.getSkuHasStock(skuIds) error {}", e.getMessage());
            }
            Set<Long> finalSkuIdSet = skuIdSet;
            for (CartItem cartItem : cartItems) {
                cartItem.setHasStock(ObjectUtils.isEmpty(finalSkuIdSet) || finalSkuIdSet.contains(cartItem.getSkuId()));
            }
        }, executor);
        Integer integration = memberRespVo.getIntegration();
        orderConfirmVo.setIntegration(integration);
        String token = UUID.randomUUID().toString().replace("-", "");
        stringRedisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberId.toString(), token, 30, TimeUnit.MINUTES);
        orderConfirmVo.setOrderToken(token);
        try {
            CompletableFuture.allOf(memberReceiveAddressesFuture, cartItemsFuture).get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        return orderConfirmVo;
    }

    //    @GlobalTransactional
    @Transactional(rollbackFor = StockInsufficientException.class)
    @Override
    public OrderSubmitRespVo orderSubmit(OrderSubmitVo orderSubmitVo) {

        orderSubmitVoThreadLocal.set(orderSubmitVo);
        //---
        OrderSubmitRespVo orderSubmitRespVo = new OrderSubmitRespVo();
        orderSubmitRespVo.setCode(0);
        String orderToken = orderSubmitVo.getOrderToken();
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long res = stringRedisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class), List.of(OrderConstant.USER_ORDER_TOKEN_PREFIX + orderSubmitVo.getAddressId()), orderToken);
        if (ObjectUtils.isEmpty(res) || 0L == res) {
            // 1 -> token invalid
            orderSubmitRespVo.setCode(1);
            return orderSubmitRespVo;
        }
        OrderCreateTo orderCreateTo = orderCreate();
        BigDecimal payAmount = orderCreateTo.getOrder().getPayAmount();
        BigDecimal payPrice = orderSubmitVo.getPayPrice();
        if (!(Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01)) {
            // 2 -> payAmount not equals payPrice
            orderSubmitRespVo.setCode(2);
            return orderSubmitRespVo;
        }
        //TODO saveOrder
        saveOrder(orderCreateTo);
        WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
        wareSkuLockVo.setOrderSn(orderCreateTo.getOrder().getOrderSn());
        List<online.foxnull2.foxmall.order.vo.wareSkuLock.OrderItem> orderItems = orderCreateTo.getOrderItems().stream().map(item -> {
            online.foxnull2.foxmall.order.vo.wareSkuLock.OrderItem orderItem = new online.foxnull2.foxmall.order.vo.wareSkuLock.OrderItem();
            orderItem.setSkuId(item.getSkuId())
                    .setSkuName(item.getSkuName())
                    .setCount(item.getSkuQuantity());
            return orderItem;
        }).collect(Collectors.toList());
        wareSkuLockVo.setOrderItems(orderItems);
        //TODO remote lock stock
        R r = wareFeignService.orderSkuLockStock(wareSkuLockVo);
        if (!Objects.equals(r.get("code").toString(), "0")) {
            // 3 -> sku stock insufficient
            orderSubmitRespVo.setCode(3);
            throw new StockInsufficientException();
        }
        online.foxnull2.foxmall.order.vo.orderSubmitResp.Order order = new online.foxnull2.foxmall.order.vo.orderSubmitResp.Order();
        BeanUtils.copyProperties(orderCreateTo.getOrder(), order);
        orderSubmitRespVo.setOrder(order);
        OrderTo orderTo = new OrderTo();
        orderTo.setOrderSn(order.getOrderSn());
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", orderTo);
        return orderSubmitRespVo;
    }

    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {
        OrderEntity orderEntity = baseMapper.selectOne(new QueryWrapper<OrderEntity>()
                .eq("order_sn", orderSn));
        log.debug("getOrderByOrderSn -> {}", orderEntity);
        return orderEntity;
    }

    @Override
    public void orderReleaseHandle(OrderTo orderTo) {
        String orderSn = orderTo.getOrderSn();
        OrderEntity orderEntity = this.getOrderByOrderSn(orderSn);
        Integer status = orderEntity.getStatus();
        if (Objects.equals(status, OrderStatusEnum.CREATE_NEW.getCode())) {
            Long id = orderEntity.getId();
            this.updateById(new OrderEntity().setId(id).setStatus(OrderStatusEnum.CANCLED.getCode()));
            try {
                rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderTo);
            } catch (Exception e) {
                //TODO Must ensure that the message is sent successfully
            }
        }
    }

    @Override
    public String pay(String orderSn) {
        OrderEntity orderEntity = this.getOrderByOrderSn(orderSn);
        String subject = orderEntity.getOrderSn();
        String outTradeNo = orderEntity.getOrderSn();
        String totalAmount = orderEntity.getPayAmount().setScale(2, RoundingMode.HALF_UP).toEngineeringString();
        try {
            // 2. 发起API调用
            AlipayTradePagePayResponse response = Factory.Payment.Page()
                    .pay(subject, outTradeNo, totalAmount, "http://member.foxmall.com/member/order/list.html");
            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                System.out.println("调用成功");
                String body = response.getBody();
                log.debug("pay -> {}", body);
                return body;
            } else {
                System.out.println("调用失败");
            }
        } catch (Exception e) {
            System.err.println("调用遭遇异常，原因：" + e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
        return "";
    }

    @Override
    public PageUtils listWithItem(Map<String, Object> params) {
        MemberRespVo memberRespVo = LoginUserInterceptor.threadLocal.get();
        Long memberId = memberRespVo.getId();
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>().eq("member_id", memberId).orderByDesc("id")
        );
        List<OrderEntity> orderEntityList = page.getRecords();
        List<OrderVo> collect = orderEntityList.stream().map(orderEntity -> {
            OrderVo orderVo = new OrderVo();
            BeanUtils.copyProperties(orderEntity, orderVo);
            String orderSn = orderEntity.getOrderSn();
            List<OrderItemEntity> orderItemEntityList = orderItemService.listOrderItemByOrderSn(orderSn);
            List<online.foxnull2.foxmall.order.vo.order.OrderItem> orderItems = orderItemEntityList.stream().map(orderItemEntity -> {
                online.foxnull2.foxmall.order.vo.order.OrderItem orderItem = new online.foxnull2.foxmall.order.vo.order.OrderItem();
                BeanUtils.copyProperties(orderItemEntity, orderItem);
                return orderItem;
            }).collect(Collectors.toList());
            orderVo.setOrderItems(orderItems);
            return orderVo;
        }).collect(Collectors.toList());

        return new PageUtils(new Page<OrderVo>()
                .setPages(page.getPages())
                .setRecords(collect)
                .setTotal(page.getTotal())
                .setSize(page.getSize())
                .setCurrent(page.getCurrent()));
    }

    @Transactional
    @Override
    public void orderSeckillHandle(SeckillOrderTo seckillOrderTo) {
        String orderSn = seckillOrderTo.getOrderSn();
        Long memberId = seckillOrderTo.getMemberId();
        BigDecimal seckillPrice = seckillOrderTo.getSeckillPrice();
        Integer num = seckillOrderTo.getNum();
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderSn);
        orderEntity.setMemberId(memberId);
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        BigDecimal payAmount = seckillPrice.multiply(new BigDecimal(num.toString()));
        orderEntity.setPayAmount(payAmount);
        this.save(orderEntity);
        Long orderId = orderEntity.getId();
        Long skuId = seckillOrderTo.getSkuId();
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        orderItemEntity.setOrderId(orderId)
                .setOrderSn(orderSn);
        orderItemEntity.setRealAmount(payAmount);
        orderItemEntity.setSkuQuantity(num);
        orderItemEntity.setSkuId(skuId);
        R res = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo spuInfoVo = res.getObj(new TypeReference<>() {
        });
        if (!ObjectUtils.isEmpty(spuInfoVo)) {
            orderItemEntity.setSpuId(spuInfoVo.getId())
                    .setSpuName(spuInfoVo.getSpuName())
                    .setCategoryId(spuInfoVo.getCatalogId());
        }
        R result = productFeignService.info(spuInfoVo.getBrandId());
        BrandVo brandVo = result.getObj(new TypeReference<>() {
        });
        if (!ObjectUtils.isEmpty(brandVo)) {
            orderItemEntity.setSpuBrand(brandVo.getName());
        }
        orderItemService.save(orderItemEntity);
    }

    private void saveOrder(OrderCreateTo orderCreateTo) {
        Order order = orderCreateTo.getOrder();
        OrderEntity orderEntity = new OrderEntity();
        BeanUtils.copyProperties(order, orderEntity);
        baseMapper.insert(orderEntity);
        Long orderId = orderEntity.getId();
        List<OrderItem> orderItems = orderCreateTo.getOrderItems();
        List<OrderItemEntity> collect = orderItems.stream().map(orderItem -> {
            OrderItemEntity orderItemEntity = new OrderItemEntity();
            BeanUtils.copyProperties(orderItem, orderItemEntity);
            orderItemEntity.setOrderId(orderId);
            return orderItemEntity;
        }).collect(Collectors.toList());
        orderItemService.saveBatch(collect);
    }

    private OrderCreateTo orderCreate() {
        OrderCreateTo orderCreateTo = new OrderCreateTo();
        Order order = buildOrder();
        orderCreateTo.setOrder(order);
        String orderSn = order.getOrderSn();
        List<OrderItem> orderItems = buildOrderItems(orderSn);
        orderCreateTo.setOrderItems(orderItems);
        if (!ObjectUtils.isEmpty(orderItems)) {
            computePrice(order, orderItems);
        }

        return orderCreateTo;
    }

    private void computePrice(Order order, List<OrderItem> orderItems) {
        BigDecimal totalAmount = new BigDecimal("0.0");
        BigDecimal promotionAmount = new BigDecimal("0.0");
        BigDecimal couponAmount = new BigDecimal("0.0");
        BigDecimal integrationAmount = new BigDecimal("0.0");
        Integer giftGrowth = 0;
        Integer giftIntegration = 0;
        for (OrderItem orderItem : orderItems) {
            totalAmount = totalAmount.add(orderItem.getRealAmount());
            promotionAmount = promotionAmount.add(orderItem.getPromotionAmount());
            couponAmount = couponAmount.add(orderItem.getCouponAmount());
            integrationAmount = integrationAmount.add(orderItem.getIntegrationAmount());
            giftGrowth += orderItem.getGiftGrowth();
            giftIntegration += orderItem.getGiftIntegration();
        }
        order.setTotalAmount(totalAmount);
        BigDecimal payAmount = totalAmount.add(order.getFreightAmount());
        order.setPayAmount(payAmount);
        order.setPromotionAmount(promotionAmount);
        order.setCouponAmount(couponAmount);
        order.setIntegrationAmount(integrationAmount);
        order.setGrowth(giftGrowth);
        order.setIntegration(giftIntegration);
    }

    private Order buildOrder() {
        MemberRespVo memberRespVo = LoginUserInterceptor.threadLocal.get();
        OrderSubmitVo orderSubmitVo = orderSubmitVoThreadLocal.get();
        String orderSn = IdWorker.getTimeId();
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setMemberId(memberRespVo.getId());
        Long addressId = orderSubmitVo.getAddressId();
        R res = wareFeignService.getFreightByAddressId(addressId);
        FreightVo freightVo = res.getObj(new TypeReference<>() {
        });
        if (!ObjectUtils.isEmpty(freightVo)) {
            BigDecimal freight = freightVo.getFreight();
            order.setFreightAmount(freight);
            online.foxnull2.foxmall.order.vo.freight.MemberReceiveAddress memberReceiveAddress = freightVo.getMemberReceiveAddress();
            order.setReceiverCity(memberReceiveAddress.getCity())
                    .setReceiverDetailAddress(memberReceiveAddress.getDetailAddress())
                    .setReceiverName(memberReceiveAddress.getName())
                    .setReceiverPhone(memberReceiveAddress.getPhone())
                    .setReceiverPostCode(memberReceiveAddress.getPostCode())
                    .setReceiverProvince(memberReceiveAddress.getProvince())
                    .setReceiverRegion(memberReceiveAddress.getRegion());
        }
        order.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        order.setAutoConfirmDay(7);
        order.setDeleteStatus(0);
        return order;
    }

    private List<OrderItem> buildOrderItems(String orderSn) {
        List<CartItem> cartItems = cartFeignService.listCartItem();
        if (!ObjectUtils.isEmpty(cartItems)) {
            List<OrderItem> collect = cartItems.stream().map(cartItem -> {
                OrderItem orderItem = buildOrderItem(cartItem);
                orderItem.setOrderSn(orderSn);
                return orderItem;
            }).collect(Collectors.toList());
//            List<OrderItem> collect = cartItems.stream().map(cartItem -> buildOrderItem(cartItem).setOrderSn(orderSn)).collect(Collectors.toList());
            log.debug("buildOrderItems -> {}", collect);
            return collect;
        }
        return null;
    }

    private OrderItem buildOrderItem(CartItem cartItem) {

        OrderItem orderItem = new OrderItem();
        Long skuId = cartItem.getSkuId();
        R res = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo spuInfoVo = res.getObj(new TypeReference<>() {
        });
        if (!ObjectUtils.isEmpty(spuInfoVo)) {
            orderItem.setSpuId(spuInfoVo.getId())
                    .setSpuName(spuInfoVo.getSpuName())
                    .setBrandId(spuInfoVo.getBrandId())
                    .setCategoryId(spuInfoVo.getCatalogId());
        }
        R result = productFeignService.info(spuInfoVo.getBrandId());
        BrandVo brandVo = result.getObj(new TypeReference<>() {
        });
        if (!ObjectUtils.isEmpty(brandVo)) {
            orderItem.setSpuBrand(brandVo.getName());
        }
        orderItem.setSkuId(skuId)
                .setSkuName(cartItem.getTitle())
                .setSkuPic(cartItem.getDefaultImg())
                .setSkuPrice(cartItem.getPrice())
                .setSkuQuantity(cartItem.getCount());
        String skuAttrsVals = StringUtils.collectionToDelimitedString(cartItem.getSkuSaleAttr(), ";");
        orderItem.setSkuAttrsVals(skuAttrsVals);
        orderItem.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue() / 2);
        orderItem.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue() / 4);
        orderItem.setPromotionAmount(new BigDecimal("0.0"))
                .setCouponAmount(new BigDecimal("0.0"))
                .setIntegrationAmount(new BigDecimal("0.0"));
        BigDecimal totalAmount = orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuQuantity().toString()));
        BigDecimal realAmount = totalAmount.subtract(orderItem.getPromotionAmount())
                .subtract(orderItem.getCouponAmount())
                .subtract(orderItem.getIntegrationAmount());
        orderItem.setRealAmount(realAmount);
        return orderItem;
    }
}
